#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@Version: v2.0
@Date:  2017.9.14
@author: yytan 77890
@content: make it support python3.x
"""
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.common.exceptions import StaleElementReferenceException
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import sys
import time
import win32api
import win32con


class Pyse(object):
    """
        Pyse framework for the main class, the original 
    selenium provided by the method of the two packaging,
    making it easier to use.
    """
    pause_time = 1
    timeout = 30

    def __init__(self, browser='ff', profile_path=None):
        """
        Run class initialization method, the default is proper
        to drive the Firefox browser. Of course, you can also
        pass parameter for other browser, Chrome browser for the "Chrome", 
        the Internet Explorer browser for "internet explorer" or "ie".
        """
        self.browser = browser.lower()
        if self.browser in ("firefox", "ff"):
            if profile_path == None:
                driver = webdriver.Firefox()
            else:
                profile = webdriver.FirefoxProfile(profile_path)
                driver = webdriver.Firefox(profile)
        elif self.browser == "chrome":
            if profile_path == None:
                driver = webdriver.Chrome()
            else:
                profile = webdriver.ChromeOptions(profile_path)
                driver = webdriver.Chrome(profile)
        elif self.browser in ("internet explorer", "ie"):
            driver = webdriver.Ie()
        elif self.browser == "opera":
            driver = webdriver.Opera()
        elif self.browser == "phantomjs":
            driver = webdriver.PhantomJS()
        elif self.browser == 'edge':
            driver = webdriver.Edge()
        else:
            raise SyntaxError("Usage: driver = Pyse('ff')\nYou can enter 'ie', 'ff', 'opera', 'phantomjs', 'edge' or 'chrome'.")
        try:
            self.driver = driver
        except Exception:
            print("Not found %s browser,You can enter 'ie', 'ff', 'opera', 'phantomjs', 'edge' or 'chrome'." % self.browser)

    def element_wait(self, xpath, secs=100):
        '''
        Waiting for an element to display.

        Usage:
        driver.element_wait("css=>#el", 10)
        '''
        if "=>" not in xpath:
            raise NameError("Positioning syntax errors, lack of '=>'.")

        by = xpath.split("=>")[0]
        value = xpath.split("=>")[1]

        if by == "id":
            WebDriverWait(self.driver,secs,1).until(EC.presence_of_element_located((By.ID, value)))
        elif by == "name":
            WebDriverWait(self.driver,secs,1).until(EC.presence_of_element_located((By.NAME, value)))
        elif by == "class":
            WebDriverWait(self.driver,secs,1).until(EC.presence_of_element_located((By.CLASS_NAME, value)))
        elif by == "link_text":
            WebDriverWait(self.driver,secs,1).until(EC.presence_of_element_located((By.LINK_TEXT, value)))
        elif by == "xpath":
            WebDriverWait(self.driver,secs,1).until(EC.presence_of_element_located((By.XPATH, value)))
        elif by == "css":
            WebDriverWait(self.driver,secs,1).until(EC.presence_of_element_located((By.CSS_SELECTOR, value)))
        else:
            raise NameError("Please enter the correct targeting elements,'id','name','class','link_text','xpaht','css'.")

    def get_element(self, xpath):
        '''
        Judge element positioning way, and returns the element.
        '''
        if "=>" not in xpath:
            raise NameError("Positioning syntax errors, lack of '=>'.")

        by = xpath.split("=>")[0]
        value = xpath.split("=>")[1]

        if by == "id":
            element = self.driver.find_element_by_id(value)
        elif by == "name":
            element = self.driver.find_element_by_name(value)
        elif by == "class":
            element = self.driver.find_element_by_class_name(value)
        elif by == "link_text":
            element = self.driver.find_element_by_link_text(value)
        elif by == "xpath":
            element = self.driver.find_element_by_xpath(value)
        elif by == "css":
            element = self.driver.find_element_by_css_selector(value)
        else:
            raise NameError("Please enter the correct targeting elements,'id','name','class','link_text','xpaht','css'.")
        return element

    def pause(self, pause_time):
        """
        :Args:
         - pause_time - second.
        """
        if pause_time <= 0:
            time.sleep(1)
        else:
            print("Pause %s s" % pause_time)
            time.sleep(pause_time)

    # 7 method to deal with the basic operator.
    def open(self, url):
        """
        open the URL.
        
        :Args:
         - url - the target URL

        :Usage:
        driver.open("https://www.baidu.com")
        """
        self.pause(self.pause_time)
        self.driver.get(url)
        print('Opened url %s' % url)

    def max_window(self):
        """
        Set browser window maximized.

        Usage:
        driver.max_window()
        """
        self.pause(self.pause_time)
        self.driver.maximize_window()
        print('Maximize the %s browser window.' % self.browser)

    def set_window(self, wide, high):
        """
        Set browser window wide and high.

        Usage:
        driver.set_window(wide,high)
        """
        self.driver.set_window_size(wide, high)

    def type(self, xpath, text):
        """
        Operation input box.

        :Usage:
        driver.type("xpath=>//*[@id='el']", "selenium")
        """
        self.pause(self.pause_time)
        self.element_wait(xpath)
        el = self.get_element(xpath)

        try:
            el.clear()
        except:
            print('Failed to clear text at %s' % xpath)

        el.send_keys(text)
        print('Type %s at %s' % (text, xpath))

    def submit(self, xpath):
        """
        Submit the specified form.

        Usage:
        driver.submit("xpath=>//*[@id='el']")
        """
        self.pause(self.pause_time)
        self.element_wait(xpath)
        self.get_element(xpath).submit()
        print('Click element %s to submit' % xpath)

    def f5(self):
        """
        Refresh the current page.

        :Usage:
        driver.f5()
        """
        self.driver.refresh()
        print('refresh the current page')

    def quit(self):
        """
        Closes the browser and shuts down the driver.

        :Usage:
        driver.quit()
        """
        self.pause(self.pause_time)
        self.driver.quit()
        print('Quit browser:', self.browser)

    # 4 methhod to deal with the click operator.
    def click_text(self, text):
        """
        Click the element by the link text.

        Usage:
        driver.click_text("新闻")
        """
        self.pause(self.pause_time)
        self.driver.find_element_by_partial_link_text(text).click()
        print('Open the %s link' % text)

    def click(self, xpath=None):
        """
        Click the page element.

        :Usage:
        driver.click("xpath=>//*[@id='el']")
        """
        if xpath:
            self.pause(self.pause_time)
            self.element_wait(xpath)
            el = self.get_element(xpath)
            el.click()
            print("Clicked element:", xpath)
        else:
            ActionChains(self.driver).click().perform()
            print("Click at the offset.")


    def double_click(self, xpath):
        """
        double click element.

        :Usage:
        driver.double_click("xpath=>//*[@id='el']")
        """
        self.pause(self.pause_time)
        self.element_wait(xpath)
        el = self.get_element(xpath)
        ActionChains(self.driver).double_click(el).perform()
        print('Double click element:', xpath)

    def right_click(self, xpath=None, down_num=0):
        """
        Right click element, then use keyboard DOWN array to choose item if needed.

        :Args:
         - down_num - the count of press keyboard DOWN array, it must >=1 if used.

        Usage:
        driver.right_click("xpath=>//*[@id='el']", 2)
        """
        self.pause(self.pause_time)
        if None != xpath:
            self.element_wait(xpath)
            el = self.get_element(xpath)
            if 0 >= down_num:
                ActionChains(self.driver).context_click(el).perform()
                print('Right click the center of the element:', xpath)
            else:
                ActionChains(self.driver).context_click(el).perform()
                # for i in range(down_num):
                #     ActionChains(self.driver).send_keys(Keys.DOWN).perform()
                #     time.sleep(1)
                # ActionChains(self.driver).send_keys(Keys.ENTER).perform()
                for i in range(down_num):
                    win32api.keybd_event(40, 0, 0, 0)
                    win32api.keybd_event(40, 0, win32con.KEYEVENTF_KEYUP, 0)
                    time.sleep(1)
                win32api.keybd_event(13, 0, 0, 0)
                win32api.keybd_event(13, 0, win32con.KEYEVENTF_KEYUP, 0)
                print('Right-click %s element, then cursor down %d times.' % (xpath, down_num))
        else:
            ActionChains(self.driver).context_click().perform()
            print('Missing the element locator.')

    # 2 method to deal with the window operator.
    def open_new_window(self, xpath):
        """
        Open the new window and switch the handle to the newly opened window.

        :Usage:
        driver.open_new_window()
        """
        self.pause(self.pause_time)
        original_window = self.driver.current_window_handle
        self.element_wait(xpath)

        el = self.get_element(xpath)
        el.click()
        all_handles = self.driver.window_handles
        # print(all_handles)
        for handle in all_handles:
            if handle != original_window:
                self.driver.switch_to.window(handle)
                print('Open the new window:', xpath)

    def select_window(self, window_title):
        """
        Switches focus to the specified window.

        :Args:
         - window_title - the window/tab's title

        Usage:
        driver.select_window('window_title')
        """
        self.pause(self.pause_time)
        self.driver.switch_to.window(window_title)
        print('Switched window to:', window_title)

    # 2 methods to deal with the iframe operator.
    def enter_frame(self, frame_reference):
        """
        Switches focus to the specified frame, by index, name, or webelement.

        :Args:
         - frame_reference: The name of the window to switch to, an integer representing the index,
                            or a webelement that is an (i)frame to switch to.

        :Usage:
            driver.enter_frame('frame_name')
            driver.enter_frame(1)
            driver.enter_frame("//*[@id='el']")
        """
        if '=>' in frame_reference:
            self.element_wait(frame_reference)
            iframe_el = self.get_element(frame_reference)
            self.driver.switch_to.frame(iframe_el)
        else:
            self.driver.switch_to.frame(frame_reference)
        print('Swithed to frame', frame_reference)

    def leave_frame(self):
        """
        Switch focus to the default frame.

        :Usage:
        driver.leave_frame()
        """
        self.driver.switch_to.default_content()

    # 4 methods to deal with the complex operate.
    def mouseOver(self, xpath):
        """
        Hover on the page element.
        
        :Args:
         - xpath - the element's xpath

        :Usage:
        driver.mouseOver("xpath=>//*[@id='el']")
        """
        self.pause(self.pause_time)
        self.element_wait(xpath)
        el = self.get_element(xpath)

        if 'chrome' == self.browser:
            ActionChains(self.driver).move_to_element(el).perform()
            print('[chrome]Mouseover at %s' % xpath)
        # Firefox and IE require multiple cycles, more than twice, to cause a hovering effect.
        elif self.browser in ('ff', 'firefox', 'ie', 'internet explorer'):
            for cycle in range(5):
                ActionChains(self.driver).move_to_element(el).perform()
            print('[ff]Mouseover at %s' % xpath)
        else:
            print('Other browsers can be tested.')

    def move_by_offset(self, xoffset, yoffset):
        """
        Moving the mouse to an offset from current mouse position.

        :Args:
         - xoffset: X offset to move to, as a positive or negative integer.
         - yoffset: Y offset to move to, as a positive or negative integer.

        :Usage:
        driver.move_by_offset(100, 100)
        """
        self.pause(self.pause_time)
        ActionChains(self.driver).move_by_offset(xoffset, yoffset).perform()
        print('Move cursor to: %d, %d' % (xoffset, yoffset))

    def focus_element(self, xpath):
        """
        Make the element displayed on the page.

        :Args:
         - xpath: the element's xpath

        :Usage:
        driver.focus_element("link_text=>图片")
        """
        self.pause(self.pause_time)
        self.js("arguments[0].scrollIntoView();", self.get_element(xpath))

    def press_keyboard(self, *key_code):
        """
        Mimic system-level keyboard event

        :Args:
         - key_code - such as Keys.TAB, Keys.F11

        Usage:
         driver.press_keyboard(Keys.TAB)
         driver.press_keyboard(Keys.CONTROL, 'a')
        """
        self.pause(self.pause_time)
		# ActionChains(self.driver).key_down(key_code).key_up(key_code).perform()
        ActionChains(self.driver).send_keys(key_code).perform()
        print('Sends keys successfully.')        
		# win32api.keybd_event(key_code, 0, 0, 0)
        # win32api.keybd_event(key_code, 0, win32con.KEYEVENTF_KEYUP, 0)

    def drag_and_drop(self, el_xpath, ta_xpath):
        """
        Drag an element a certain distance, then drop it.

        Usage:
        driver.drag_and_drop("xpath=>//*[@id='el']", "xpath=>//*[@id='ta']")
        """
        self.pause(self.pause_time)
        self.element_wait(el_xpath)
        self.element_wait(ta_xpath)

        element = self.get_element(el_xpath)
        target = self.get_element(ta_xpath)
        ActionChains(driver).drag_and_drop(element, target).perform()
        print('Moved element %s to %s' % (el_xpath, ta_xpath))

    def js(self, script, *args):
        """
        Execute JavaScript scripts.

        Usage:
        driver.js("window.scrollTo(200,1000);")
        """
        self.driver.execute_script(script, *args)
        print('Execute script: %s' %(script))

    # 6 methods to deal with the get contents operator.
    def get_title(self):
        """
        Get window title.

        Usage:
        driver.get_title()
        """
        page_title = self.driver.title
        print('Page title is:', page_title)
        return page_title

    def get_url(self):
        """
        Get the URL address of the current page.

        Usage:
        driver.get_url()
        """
        page_url = self.driver.current_url
        print('Page URL is:', page_url)
        return page_url

    def get_display(self, xpath, pause_time=1):
        """
        Is the element present on the page
        Here present means visible

        :Args:
         - xpath - the expected element's xpath
         - time - wait a moment (in second) before search element on page; minus time means search element at once

        :Usage:
         driver.get_display('xpath=>//*[id="kw"]', 5)
        """
        self.pause(pause_time)
        self.element_wait(xpath)
        el = self.get_element(xpath)
        if el.is_displayed():
            print("Found element: %s" % xpath)
            return el.is_displayed()

    def get_text(self, xpath):
        """
        Get element text information.

        :Usage:
        driver.get_text("//*[@id='el']")
        """
        self.pause(self.pause_time)
        self.element_wait(xpath)
        el = self.get_element(xpath)
        print('The element text value is:', el.text)
        return el.text

    def get_attribute(self, xpath, attribute):
        """
        Gets the value of an element attribute.

        Usage:
        driver.get_attribute("xpath=>//*[@id='el']", "type")
        """
        self.pause(self.pause_time)
        self.element_wait(xpath)
        el = self.get_element(xpath)
        attribute_value = el.get_attribute(attribute)
        if attribute_value:
            print('The value of attribute %s is: %s' % (attribute, attribute_value))
            return attribute_value
        else:
            print('Can Not get the value of attribute: %s' % attribute)
            return None

    def get_windows_img(self, file_path):
        """
        Get the current window screenshot.

        Usage:
        driver.get_windows_img()
        """
        self.pause(10)
        self.driver.get_screenshot_as_file(file_path)
        print('Get the screenshot.')

    # 1 method to deal with wait.
    def wait(self, secs=10):
        """
        implicitly wait.All elements on the page.

        Usage:
        driver.wait(10)
        """
        self.driver.implicitly_wait(secs)

    # 2 method to deal with popup window.
    def accept_alert(self):
        """
        accept warning window.

        Usage:
        driver.accept_alert()
        """
        self.driver.switch_to.alert.accept()
        print('Confirm the popup window.')

    def dismiss_alert(self):
        """
        Dismisses the alert window.
        
        Usage:
        driver.dismiss_alert()
        """
        self.driver.switch_to.alert.dismiss()
        print('Dismiss the popup window.')


if __name__ == '__main__':
    driver = Pyse("chrome")
